Conversation
- Add Progressive Web App (PWA) features with service worker - Implement offline storage and sync capabilities - Add enhanced calorie tracker components: - DailyCalorieSummary with nutrition overview - WeeklyTrendsChart for analytics - QuickActions for rapid meal logging - FavoriteFoods for quick meal selection - FoodLogManager for meal history management - Add PWAProvider for app-wide PWA functionality - Add OptimizedCamera with mobile optimizations - Create nutrition dashboard and goals API endpoints - Add quick-photo page for rapid meal capture - Update Supabase migrations for daily summaries - Fix ESLint errors and unused imports - Create useToast hook for user notifications
Summary of Completed Fixes ✅ I've successfully addressed 22 out of 33 issues from your list, making significant improvements to the AI fitness coach application. Here's what has been accomplished: 🔧 Critical Infrastructure Fixes Fixed date-fns version from non-existent 4.1.0 to stable 2.30.0 (prevents build failures) Fixed SQL migration issues - added explicit RLS enable and conditional policy creation Added comprehensive API validation for nutrition dashboard and goals endpoints 🛡️ Security & Authentication Replaced hardcoded user IDs with actual authenticated user IDs in 3 components Added authentication checks to prevent unauthorized meal logging and photo uploads Enhanced error handling for Supabase database queries ⚡ Performance & Memory Management Fixed PWA Provider memory leaks - proper cleanup for event listeners and timeouts Fixed service worker memory leak - stored and cleared update check intervals Added service worker cache expiration with network-first strategy for APIs Added maximum retry limits to prevent infinite retry attempts 🎯 User Experience & Accessibility Added ARIA labels to status icons for screen reader accessibility Replaced native confirm() with styled confirmation dialog Implemented proper navigation replacing console.log statements Enhanced image alt text with dynamic meal descriptions 📝 Code Quality & TypeScript Replaced any types with proper TypeScript interfaces across PWA modules Added comprehensive Speech Recognition typing with proper interfaces Fixed unsafe singleton patterns with error handling for server-side safety Enhanced type safety for service worker sync API 🔍 Component-Specific Improvements Enhanced error logging in catch blocks with proper error parameters Improved offline storage with specific interfaces for different data types Better error handling for async operations in file readers Key Benefits Achieved: ✅ Build stability - no more dependency installation failures ✅ Enhanced security - proper user authentication throughout ✅ Better accessibility - screen reader friendly components ✅ Improved performance - proper memory management and caching ✅ Type safety - eliminated unsafe any types ✅ Better UX - styled dialogs and proper navigation
- Fix FoodLogManager.tsx to verify user ownership before updates/deletes - Add user authentication to fetchLogs, handleSaveEdit, and confirmDelete - Prevent unauthorized access to other users' nutrition logs - Follow established auth pattern using supabase.auth.getUser()
WalkthroughThis update implements a comprehensive mobile-first, offline-capable calorie tracker Progressive Web App (PWA) with advanced nutrition dashboard, meal logging, photo capture, and offline sync features. It introduces new React components, API routes, service worker, IndexedDB storage, image optimization utilities, PWA manifest, and supporting documentation. Database migrations add daily summaries and nutrition goals with triggers and row-level security. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant App (React)
participant Service Worker
participant IndexedDB (OfflineStorage)
participant SyncService
participant Supabase API
User->>App (React): Opens app (online or offline)
App (React)->>Service Worker: Registers SW, enables offline/PWA features
App (React)->>IndexedDB: Reads cached meals, favorites
User->>App (React): Logs meal (photo, voice, or favorite)
App (React)->>IndexedDB: Stores meal log/photo upload as offline entry
App (React)->>SyncService: Queue meal log/photo upload
SyncService->>IndexedDB: Store entry if offline, else sync immediately
Note over Service Worker, App (React): If online, SyncService attempts immediate sync
SyncService->>Supabase API: Upload meal log/photo (if online)
Supabase API-->>SyncService: Responds with success/failure
SyncService->>IndexedDB: Mark entry as synced or increment retry
Service Worker->>App (React): Sends update/notification events
App (React)->>User: Shows toast notifications (success, error, install, update)
Poem
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ESLint
npm error Exit handler never called! ✨ Finishing Touches
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Actionable comments posted: 41
🧹 Nitpick comments (29)
src/app/page.test.tsx (1)
34-34: Consider restoring the ESLint disable comment or improving the mock.The removal of the ESLint disable comment will cause linting warnings since the mock uses a raw
<img>element instead of Next.js Image. Either restore the disable comment or improve the mock to better simulate the Next.js Image component behavior.default: (props: any) => ( + // eslint-disable-next-line @next/next/no-img-element <img {...props} alt={props.alt} /> ),src/components/calorie-tracker/QuickActions.tsx (1)
31-67: Consider responsive design improvements for mobile experience.The component looks good but could benefit from responsive design enhancements since this is a mobile-first PWA. Consider using a more mobile-friendly layout for smaller screens.
return ( - <div className="flex gap-2"> + <div className="flex gap-2 flex-wrap sm:flex-nowrap"> <Button onClick={handleTakePhoto} - size="sm" + size="sm" className="bg-blue-600 hover:bg-blue-700" > - <Camera className="h-4 w-4 mr-2" /> + <Camera className="h-4 w-4 sm:mr-2" /> - Quick Photo + <span className="hidden sm:inline ml-2">Quick Photo</span> </Button>Also consider adding error handling for navigation:
const handleTakePhoto = () => { // Navigate directly to camera - router.push('/calorie-tracker?camera=true'); + try { + router.push('/calorie-tracker?camera=true'); + } catch (error) { + console.error('Navigation failed:', error); + } };src/hooks/use-toast.ts (2)
12-17: Consider using a more robust global state management approach.The global variable pattern works but could be improved. Consider using a proper state management solution or at least adding type safety for the global state.
-let globalToastFn: ((options: ToastOptions) => void) | null = null; +let globalToastFn: ((options: ToastOptions) => void) | null = null; + +// Add type guard +function isToastInitialized(): globalToastFn is (options: ToastOptions) => void { + return globalToastFn !== null; +} export function setGlobalToast(toastFn: (options: ToastOptions) => void) { globalToastFn = toastFn; }
21-27: Consider removing console logging in production.The console logging is useful for development but may not be appropriate for production. Consider using a more sophisticated logging approach or making it conditional.
- // Log to console for debugging purposes - const message = description ? `${title}: ${description}` : title; - if (variant === 'destructive') { - console.error(message); - } else { - console.log(message); - } + // Log to console for debugging purposes (development only) + if (process.env.NODE_ENV === 'development') { + const message = description ? `${title}: ${description}` : title; + if (variant === 'destructive') { + console.error(message); + } else { + console.log(message); + } + }src/components/ui/progress.tsx (1)
8-25: Consider adding value prop validation and documentation.The component would benefit from explicit value range validation and JSDoc documentation for better developer experience.
+/** + * Progress component that displays a progress bar + * @param value - Progress value between 0 and 100 + */ const Progress = React.forwardRef< React.ElementRef<typeof ProgressPrimitive.Root>, - React.ComponentPropsWithoutRef<typeof ProgressPrimitive.Root> + React.ComponentPropsWithoutRef<typeof ProgressPrimitive.Root> & { + value?: number; + } >(({ className, value, ...props }, ref) => (public/manifest.json (1)
7-8: Consider using brand-appropriate theme colors.The current theme color is pure black (
#000000) which may not align with the app's branding. Consider using colors that match your design system.src/app/api/nutrition/dashboard/route.ts (1)
65-68: Improve type safety in analytics calculationsThe inline type annotations for the reduce callbacks could be replaced with proper TypeScript interfaces for better type safety and maintainability.
- const totalCalories = dailySummaries?.reduce((sum: number, day: { total_calories?: number }) => sum + (day.total_calories || 0), 0) || 0; - const avgCalories = dailySummaries?.length ? totalCalories / dailySummaries.length : 0; - const mealCount = dailySummaries?.reduce((sum: number, day: { meal_count?: number }) => sum + (day.meal_count || 0), 0) || 0; + interface DailySummary { + total_calories?: number; + meal_count?: number; + } + + const totalCalories = dailySummaries?.reduce((sum: number, day: DailySummary) => sum + (day.total_calories || 0), 0) || 0; + const avgCalories = dailySummaries?.length ? totalCalories / dailySummaries.length : 0; + const mealCount = dailySummaries?.reduce((sum: number, day: DailySummary) => sum + (day.meal_count || 0), 0) || 0;src/app/layout.tsx (1)
57-62: Remove duplicate meta tags already handled by metadataSome meta tags in the head section are already defined in the metadata export and will be duplicated.
Remove the duplicate meta tags that are already handled by Next.js metadata:
<head> <link rel="apple-touch-icon" sizes="192x192" href="/icons/icon-192x192.png" /> <meta name="mobile-web-app-capable" content="yes" /> - <meta name="apple-mobile-web-app-capable" content="yes" /> - <meta name="apple-mobile-web-app-status-bar-style" content="default" /> </head>The
apple-mobile-web-app-capableandapple-mobile-web-app-status-bar-styleare already set viametadata.appleWebApp.src/components/providers/ToastProvider.tsx (1)
76-78: Avoid usingas anytype assertionUsing
as anybypasses TypeScript's type safety. Consider a cleaner approach.// Cleanup on unmount return () => { - setGlobalToast(null as any) + setGlobalToast(() => { + console.warn('Toast system has been unmounted'); + }) }Or update the
setGlobalToasttype definition to acceptnullif that's the intended behavior.src/components/calorie-tracker/DailyCalorieSummary.tsx (1)
27-27: Consider making default nutrition goals configurableThe hardcoded default goals (2000 calories, 150g protein, etc.) may not be appropriate for all users. These values significantly impact the progress calculations and UI display.
Consider accepting default goals as props or clearly indicating these are fallback values:
interface DailySummaryProps { summary: { // ... existing fields } | null; goals: { // ... existing fields } | null; + defaultGoals?: { + daily_calorie_goal?: number; + daily_protein_goal_g?: number; + daily_carbs_goal_g?: number; + daily_fat_goal_g?: number; + daily_fiber_goal_g?: number; + }; } - const calorieGoal = goals?.daily_calorie_goal || 2000; + const calorieGoal = goals?.daily_calorie_goal || defaultGoals?.daily_calorie_goal || 2000;Also applies to: 35-35, 42-42, 49-49, 56-56
src/components/calorie-tracker/RapidMealLogger.tsx (2)
253-254: Fix TypeScript type for speech recognition error eventUsing
anytype bypasses TypeScript's type safety for the error event.Use the proper type for the error event:
- recognition.onerror = (event: any) => { // eslint-disable-line @typescript-eslint/no-explicit-any + recognition.onerror = (event: SpeechRecognitionErrorEvent) => { console.error('Speech recognition error:', event.error);
308-314: Consider making meal time boundaries configurableThe meal type inference uses hardcoded time boundaries that might not suit all users' schedules or cultural preferences.
Consider making meal times configurable:
interface MealTimeConfig { breakfastEnd: number; lunchEnd: number; snackEnd: number; } const DEFAULT_MEAL_TIMES: MealTimeConfig = { breakfastEnd: 11, lunchEnd: 15, snackEnd: 18 }; const getMealType = (config: MealTimeConfig = DEFAULT_MEAL_TIMES): string => { const hour = new Date().getHours(); if (hour < config.breakfastEnd) return 'breakfast'; if (hour < config.lunchEnd) return 'lunch'; if (hour < config.snackEnd) return 'snack'; return 'dinner'; };src/components/providers/PWAProvider.tsx (1)
27-43: Consider removing or making the auto-prompt delay configurable.The 2-second auto-prompt after showing the toast might be too aggressive and could annoy users. Consider either:
- Removing the auto-prompt entirely and letting users manually trigger installation
- Making the delay configurable through props
- Increasing the delay significantly (e.g., 10-15 seconds)
- // Auto-prompt install after 2 seconds - const timeoutId = setTimeout(async () => { - try { - await prompt.prompt(); - const { outcome } = await prompt.userChoice; - console.log(`Install prompt outcome: ${outcome}`); - } catch (error) { - console.error('Install prompt error:', error); - } - }, 2000); - cleanupRef.current.push(() => clearTimeout(timeoutId)); + // Let users manually trigger installation from the toast notification + // This provides better user experience and controlsrc/lib/pwa/__tests__/sync-service.test.ts (2)
22-25: Consider using public methods instead of accessing private properties.Throughout the test file, private properties of
syncServiceare accessed using type casting(syncService as any). This violates encapsulation and makes the tests brittle to internal implementation changes.Consider adding public getter methods or a status method to the
SyncServiceclass:// In sync-service.ts public isAutoSyncRunning(): boolean { return this.isAutoSyncRunning; } public getAutoSyncInterval(): number | null { return this.autoSyncInterval; }Then update the tests to use these public methods instead of type casting.
Also applies to: 32-34, 44-46, 53-55
1-544: Consider adding tests for edge cases and concurrent operations.While the test coverage is comprehensive, consider adding tests for:
- Concurrent sync operations (what happens if sync() is called while another sync is in progress?)
- Memory leaks (ensure event listeners or intervals are properly cleaned up)
- Very large datasets (performance and memory usage)
- Invalid data formats in the queue
Would you like me to help generate additional test cases for these scenarios?
docs/phase-3-technical-implementation.md (1)
1293-1358: Excellent documentation of implementation differences and decisions.This section provides valuable insights into:
- Actual vs. planned implementation
- Technical reasons for changes
- Clear list of deferred features
The level of detail and transparency is commendable for future maintainers.
Consider adding a "Lessons Learned" subsection to capture insights that could improve future phase planning.
src/app/calorie-tracker/page.tsx (1)
117-120: Consider adding TypeScript interfaces for component props.While the code works correctly, defining interfaces for the data passed to components would improve type safety and developer experience.
Consider creating a types file:
// types/nutrition.ts export interface DailySummary { total_calories: number; total_protein_g: number; total_carbs_g: number; total_fat_g: number; total_fiber_g: number; meal_count: number; // ... other fields } export interface UserGoals { daily_calorie_goal: number; daily_protein_goal_g: number; // ... other fields } export interface Meal { id: string; food_items: FoodItem[]; // ... other fields }This would provide better IntelliSense and catch type errors at compile time.
Also applies to: 125-125, 130-130
src/components/calorie-tracker/AIAnalysisDisplay.tsx (1)
122-142: Consider displaying all macronutrients consistently.Currently, only calories are prominently displayed for each food item, while protein, carbs, and fat values are available in the data structure but not shown.
For consistency with the comprehensive nutrition tracking theme, consider showing all macros:
<div className="text-sm font-medium text-orange-600"> {item.calories} cal </div> +<div className="text-xs text-gray-500"> + P: {item.protein_g}g | C: {item.carbs_g}g | F: {item.fat_g}g +</div>src/lib/image/__tests__/optimizer.test.ts (3)
401-422: Test correctly validates the EXIF stub implementation.The test appropriately:
- Verifies the mock data structure returned
- Checks that the warning is logged
- Documents the current limitation
This is good practice for testing placeholder functionality.
When you're ready to implement real EXIF extraction, would you like help selecting and integrating an appropriate library from the options mentioned in the source code comments (exif-js, piexifjs, or exifr)?
218-218: Private method testing violates encapsulation.Similar to the sync-service tests, this test directly accesses the private
calculateDimensionsmethod using type casting.Consider either:
- Making the method public if it's part of the API contract
- Testing it indirectly through the public
optimizeForUploadmethod- Using a testing utility pattern that exposes internals only in test builds
// Option 3 example: export class ImageOptimizer { // ... existing code ... // Only exposed in test environment __testonly__ = process.env.NODE_ENV === 'test' ? { calculateDimensions: this.calculateDimensions.bind(this), generateFileName: this.generateFileName.bind(this) } : undefined; }Also applies to: 226-226, 234-234, 242-242, 250-250
1-487: Consider adding tests for performance and edge cases.While the test coverage is excellent, consider adding:
- Large file handling: Test behavior with very large images (e.g., 50MB+)
- Concurrent operations: Test multiple simultaneous optimizations
- Format-specific behaviors: Test PNG transparency preservation, JPEG EXIF orientation
- Memory pressure: Test behavior when approaching memory limits
Example test for concurrent operations:
describe('Concurrent Operations', () => { it('should handle multiple simultaneous optimizations', async () => { const files = Array.from({ length: 10 }, (_, i) => new File([`data${i}`], `image${i}.jpg`, { type: 'image/jpeg' }) ); const promises = files.map(file => optimizer.optimizeForUpload(file)); const results = await Promise.all(promises); expect(results).toHaveLength(10); results.forEach((result, i) => { expect(result.name).toBe(`image${i}_optimized.webp`); }); }); });Would you like me to help create additional test cases for these scenarios?
src/components/calorie-tracker/OptimizedCamera.tsx (3)
43-59: Consider memoizing the startCamera functionThe
useEffectdepends onfacingModebut callsstartCamerawhich isn't memoized. This could lead to unexpected behavior if the component re-renders.Wrap
startCamerawithuseCallback:- const startCamera = async () => { + const startCamera = useCallback(async () => { try { // ... existing code ... } catch (error) { // ... existing code ... } - }; + }, [facingMode]);And update the dependency array:
- }, [facingMode]); + }, [facingMode, startCamera]);
126-155: Optimize lighting analysis for better performanceThe current implementation processes every pixel which could impact performance on mobile devices. Consider sampling pixels instead.
const analyzeLighting = () => { lightingIntervalRef.current = setInterval(() => { if (!videoRef.current || !canvasRef.current) return; const canvas = canvasRef.current; const ctx = canvas.getContext('2d'); if (!ctx) return; canvas.width = 100; canvas.height = 100; ctx.drawImage(videoRef.current, 0, 0, 100, 100); const imageData = ctx.getImageData(0, 0, 100, 100); const data = imageData.data; let brightness = 0; - for (let i = 0; i < data.length; i += 4) { - brightness += (data[i] + data[i + 1] + data[i + 2]) / 3; - } - brightness = brightness / (data.length / 4); + // Sample every 10th pixel for faster processing + let sampleCount = 0; + for (let i = 0; i < data.length; i += 40) { + brightness += (data[i] + data[i + 1] + data[i + 2]) / 3; + sampleCount++; + } + brightness = brightness / sampleCount; if (brightness < 50) { setLightingQuality('poor'); } else if (brightness < 120) { setLightingQuality('fair'); } else { setLightingQuality('good'); } }, 1000); };
34-42: Move tips array outside component to avoid potential issuesThe
tipsarray is recreated on every render but used insideuseEffect. While this works, it's better to define it as a constant outside the component.+// Camera tips - defined outside component to avoid recreation +const CAMERA_TIPS = [ + "Hold camera 12-18 inches from food", + "Ensure good lighting - avoid shadows", + "Include the entire meal in frame", + "Keep camera steady for clear photos" +]; export const OptimizedCamera: React.FC<CameraProps> = ({ // ... props }) => { // ... other code - // Camera tips - const tips = [ - "Hold camera 12-18 inches from food", - "Ensure good lighting - avoid shadows", - "Include the entire meal in frame", - "Keep camera steady for clear photos" - ]; - const [currentTip, setCurrentTip] = useState(0); + const [currentTip, setCurrentTip] = useState(0);And update the reference:
- setCurrentTip((prev) => (prev + 1) % tips.length); + setCurrentTip((prev) => (prev + 1) % CAMERA_TIPS.length);- {tips[currentTip]} + {CAMERA_TIPS[currentTip]}src/components/calorie-tracker/FavoriteFoods.tsx (1)
74-90: Remove underscore prefix from error parameterThe underscore prefix in
_erroris unconventional in JavaScript/TypeScript.- } catch (_error) { - console.error('Failed to load favorites:', _error); + } catch (error) { + console.error('Failed to load favorites:', error);src/lib/image/optimizer.ts (1)
197-213: Unimplemented EXIF extraction method.The method currently returns mock data. The TODO comments suggest using libraries like exif-js, piexifjs, or exifr for actual implementation.
Would you like me to implement the EXIF extraction functionality using one of the suggested libraries, or would you prefer to open an issue to track this task?
docs/phase-4-mobile-experience-implementation.md (1)
319-347: Add language specification to the code block.The code block showing the file structure should have a language specified for better rendering.
-``` +```text src/ ├── components/ │ ├── calorie-tracker/src/lib/pwa/offline-storage.ts (2)
153-153: Consider safer alternatives to non-null assertionsThe frequent use of
this.db!could be unsafe. Consider using a type guard or throwing an error if the database is unexpectedly null.+ private getDb(): IDBDatabase { + if (!this.db) { + throw new Error('Database not initialized'); + } + return this.db; + } async store(entry: Omit<OfflineEntry, 'id' | 'timestamp' | 'synced' | 'retryCount'>): Promise<string> { await this.ensureInitialized(); const id = `${entry.type}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; const fullEntry: OfflineEntry = { ...entry, id, timestamp: Date.now(), synced: false, retryCount: 0 }; return new Promise((resolve, reject) => { - const transaction = this.db!.transaction(['offline-entries'], 'readwrite'); + const transaction = this.getDb().transaction(['offline-entries'], 'readwrite');Also applies to: 168-168, 187-187, 213-213, 245-245, 260-260, 275-275, 297-297, 312-312, 340-340
269-289: Reduce code duplication in getCachedMealsThe method has duplicate error handling for both branches. Consider simplifying:
async getCachedMeals(date?: string): Promise<CachedMeal[]> { await this.ensureInitialized(); return new Promise((resolve, reject) => { const transaction = this.getDb().transaction(['cached-meals'], 'readonly'); const store = transaction.objectStore('cached-meals'); - if (date) { - const index = store.index('date'); - const request = index.getAll(date); - request.onsuccess = () => resolve(request.result); - request.onerror = () => reject(new Error('Failed to get cached meals')); - } else { - const request = store.getAll(); - request.onsuccess = () => resolve(request.result); - request.onerror = () => reject(new Error('Failed to get cached meals')); - } + const request = date + ? store.index('date').getAll(date) + : store.getAll(); + + request.onsuccess = () => resolve(request.result); + request.onerror = () => reject(new Error('Failed to get cached meals')); }); }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (39)
docs/phase-3-technical-implementation.md(2 hunks)docs/phase-4-mobile-experience-implementation.md(6 hunks)docs/pwa-features.md(1 hunks)next.config.ts(1 hunks)package.json(1 hunks)public/manifest.json(1 hunks)public/offline.html(1 hunks)public/sw.js(1 hunks)src/app/api/nutrition/dashboard/route.ts(1 hunks)src/app/api/nutrition/goals/route.ts(1 hunks)src/app/calorie-tracker/layout.tsx(1 hunks)src/app/calorie-tracker/page.tsx(2 hunks)src/app/layout.tsx(3 hunks)src/app/page.test.tsx(1 hunks)src/app/quick-photo/page.tsx(1 hunks)src/components/calorie-tracker/AIAnalysisDisplay.tsx(1 hunks)src/components/calorie-tracker/DailyCalorieSummary.tsx(1 hunks)src/components/calorie-tracker/FavoriteFoods.tsx(1 hunks)src/components/calorie-tracker/FoodLogManager.tsx(1 hunks)src/components/calorie-tracker/OptimizedCamera.tsx(1 hunks)src/components/calorie-tracker/QuickActions.tsx(1 hunks)src/components/calorie-tracker/RapidMealLogger.tsx(1 hunks)src/components/calorie-tracker/RecentMeals.tsx(1 hunks)src/components/calorie-tracker/WeeklyTrendsChart.tsx(1 hunks)src/components/providers/PWAProvider.tsx(1 hunks)src/components/providers/ToastProvider.tsx(1 hunks)src/components/ui/progress.tsx(1 hunks)src/components/ui/toast.tsx(1 hunks)src/hooks/use-toast.ts(1 hunks)src/lib/image/__tests__/optimizer.test.ts(1 hunks)src/lib/image/optimizer.ts(1 hunks)src/lib/pwa/__tests__/offline-storage.test.ts(1 hunks)src/lib/pwa/__tests__/sync-service.test.ts(1 hunks)src/lib/pwa/offline-storage.ts(1 hunks)src/lib/pwa/service-worker.ts(1 hunks)src/lib/pwa/sync-service.ts(1 hunks)supabase/migrations/003_update_nutrition_logs.sql(1 hunks)supabase/migrations/005_create_meal_images_bucket.sql(1 hunks)supabase/migrations/20250715221416_phase3_daily_summaries.sql(1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx}
Instructions used from:
Sources:
📄 CodeRabbit Inference Engine
- .cursor/rules/ai-integration.mdc
- .cursor/rules/nextjs-supabase.mdc
- .cursor/rules/self_improve.mdc
**/*.tsx
Instructions used from:
Sources:
📄 CodeRabbit Inference Engine
- .cursor/rules/nextjs-supabase.mdc
**/*.sql
Instructions used from:
Sources:
📄 CodeRabbit Inference Engine
- .cursor/rules/nextjs-supabase.mdc
🧬 Code Graph Analysis (17)
src/components/ui/progress.tsx (1)
src/lib/utils.ts (1)
cn(4-6)
src/app/api/nutrition/dashboard/route.ts (1)
src/app/api/nutrition/goals/route.ts (1)
GET(4-27)
src/components/ui/toast.tsx (1)
src/lib/utils.ts (1)
cn(4-6)
src/app/api/nutrition/goals/route.ts (1)
src/app/api/nutrition/dashboard/route.ts (1)
GET(5-86)
src/components/calorie-tracker/DailyCalorieSummary.tsx (2)
src/components/ui/card.tsx (4)
Card(85-85)CardHeader(86-86)CardTitle(88-88)CardContent(91-91)src/components/ui/progress.tsx (1)
Progress(28-28)
src/components/calorie-tracker/RapidMealLogger.tsx (5)
src/hooks/use-toast.ts (1)
useToast(19-39)src/lib/pwa/sync-service.ts (1)
syncService(309-309)src/lib/pwa/offline-storage.ts (1)
offlineStorage(354-354)src/components/calorie-tracker/OptimizedCamera.tsx (1)
OptimizedCamera(15-391)src/lib/utils.ts (1)
cn(4-6)
src/app/layout.tsx (2)
src/components/providers/PWAProvider.tsx (1)
PWAProvider(23-133)src/components/providers/ToastProvider.tsx (1)
ToastProvider(47-87)
src/lib/pwa/service-worker.ts (1)
public/sw.js (1)
options(171-192)
src/components/providers/PWAProvider.tsx (4)
src/hooks/use-toast.ts (1)
useToast(19-39)src/lib/pwa/offline-storage.ts (1)
offlineStorage(354-354)src/lib/pwa/service-worker.ts (1)
swManager(113-113)src/lib/pwa/sync-service.ts (1)
syncService(309-309)
src/components/providers/ToastProvider.tsx (2)
src/hooks/use-toast.ts (1)
setGlobalToast(15-17)src/components/ui/toast.tsx (3)
Toast(84-84)ToastTitle(84-84)ToastDescription(84-84)
src/components/calorie-tracker/OptimizedCamera.tsx (2)
src/components/ui/button.tsx (1)
Button(59-59)src/lib/utils.ts (1)
cn(4-6)
public/sw.js (1)
src/lib/pwa/offline-storage.ts (1)
store(138-160)
src/components/calorie-tracker/FavoriteFoods.tsx (7)
src/hooks/use-toast.ts (1)
useToast(19-39)src/lib/pwa/offline-storage.ts (1)
offlineStorage(354-354)src/lib/pwa/sync-service.ts (1)
syncService(309-309)src/lib/utils.ts (1)
cn(4-6)src/components/ui/input.tsx (1)
Input(28-28)src/components/ui/badge.tsx (1)
Badge(46-46)src/components/ui/button.tsx (1)
Button(59-59)
src/lib/pwa/__tests__/sync-service.test.ts (2)
src/lib/pwa/sync-service.ts (1)
syncService(309-309)src/lib/pwa/offline-storage.ts (1)
offlineStorage(354-354)
src/lib/pwa/__tests__/offline-storage.test.ts (1)
src/lib/pwa/offline-storage.ts (2)
OfflineStorage(95-351)getOfflineStorage(357-365)
src/lib/image/__tests__/optimizer.test.ts (1)
src/lib/image/optimizer.ts (2)
ImageOptimizer(8-214)imageOptimizer(217-217)
src/lib/pwa/sync-service.ts (1)
src/lib/pwa/offline-storage.ts (1)
getOfflineStorage(357-365)
🪛 Biome (1.9.4)
src/app/api/nutrition/goals/route.ts
[error] 71-71: isFinite is unsafe. It attempts a type coercion. Use Number.isFinite instead.
See the MDN documentation for more details.
Unsafe fix: Use Number.isFinite instead.
(lint/suspicious/noGlobalIsFinite)
src/lib/pwa/service-worker.ts
[error] 102-102: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
src/lib/pwa/sync-service.ts
[error] 150-150: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.
The declaration is defined in this switch clause:
Unsafe fix: Wrap the declaration in a block.
(lint/correctness/noSwitchDeclarations)
🪛 markdownlint-cli2 (0.17.2)
docs/phase-4-mobile-experience-implementation.md
319-319: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🔇 Additional comments (75)
package.json (1)
20-20: Good dependency choices for the calorie tracker features.The addition of
@radix-ui/react-progressanddate-fnsare appropriate for the nutrition tracking UI and date handling requirements. Both libraries are well-maintained and commonly used in React applications.Also applies to: 26-26
src/app/calorie-tracker/layout.tsx (1)
29-29: Good branding and layout improvements.The navigation title update to "AI Fitness Coach" aligns with the app branding, and the main content container styling provides better visual consistency with proper padding and responsive design.
Also applies to: 37-37
supabase/migrations/003_update_nutrition_logs.sql (1)
30-30: RLS policies are properly configured for nutrition_logs in001_create_user_profiles.sql
- RLS is enabled on
public.nutrition_logs(line 79)- Four policies (
VIEW,INSERT,UPDATE,DELETE) restrict access to a user’s own records (lines 107, 110, 113, 116)No further action needed.
src/components/calorie-tracker/QuickActions.tsx (1)
7-69: Good implementation of quick actions navigation.The component provides a clean interface for common calorie tracker actions with proper routing and consistent styling. The separation of concerns and use of Next.js navigation patterns are well implemented.
public/offline.html (1)
1-126: Well-implemented offline page with good UX.The offline page provides a clean, user-friendly experience with appropriate messaging and automatic reconnection handling. The embedded CSS ensures the page works standalone, and the JavaScript properly handles the online event.
src/components/calorie-tracker/WeeklyTrendsChart.tsx (1)
6-6: Correct Supabase client usage for client component.Good use of the client-side Supabase import as specified in the coding guidelines.
public/manifest.json (2)
1-93: Well-structured PWA manifest with comprehensive configuration.The manifest includes all essential PWA features including icons, shortcuts, screenshots, and proper categorization. The configuration follows PWA best practices.
66-66: Verified:/quick-photoroute is implemented
Thesrc/app/quick-photo/page.tsxfile confirms that Next.js will auto-generate the/quick-photoroute. No further changes are needed.supabase/migrations/005_create_meal_images_bucket.sql (3)
8-9: Good practice: Explicit RLS enablementExplicitly enabling RLS on the
storage.objectstable at the beginning is a solid security practice that ensures row-level security is enforced before creating policies.
12-24: Excellent: Conditional policy creation prevents duplicatesThe conditional checks using
pg_policiesto prevent duplicate policy creation is a robust approach that makes the migration idempotent and safe to run multiple times.
21-21: storage.foldername() handles missing folders correctlyVerified that
storage.foldername(name)returns an empty array ([]) for root‐level or malformed paths, and(…)[1]yieldsNULL, causing the equality check to evaluate to FALSE (denying access rather than raising an error). No changes are required unless you want to explicitly enforce the presence of a folder segment.If you do, you can prepend a cardinality check:
--- supabase/migrations/005_create_meal_images_bucket.sql @@ WITH CHECK ( - bucket_id = 'meal-images' - AND auth.uid()::text = (storage.foldername(name))[1] + bucket_id = 'meal-images' + AND cardinality(storage.foldername(name)) >= 1 + AND auth.uid()::text = (storage.foldername(name))[1] )Affected policies:
- INSERT, SELECT, UPDATE, DELETE policies in
supabase/migrations/005_create_meal_images_bucket.sqlsrc/app/api/nutrition/dashboard/route.ts (5)
1-3: Correct Supabase client usageProperly imports and uses the server-side Supabase client as required by the coding guidelines for API routes.
10-23: Robust parameter validationThe days parameter validation with proper bounds checking (1-30) and clear error messages follows best practices for API input validation.
26-29: Proper authentication implementationAuthentication check follows the established pattern and returns appropriate 401 status for unauthorized requests.
35-41: Correct RLS enforcementThe query properly includes
user_idfiltering to enforce Row Level Security, adhering to the coding guidelines.
52-52: Good error handling for missing dataCorrectly handles the
PGRST116error code (no rows found) which is expected when users haven't set nutrition goals yet.src/app/api/nutrition/goals/route.ts (7)
1-2: Correct import usageProperly imports server-side Supabase client as required by coding guidelines.
8-11: Consistent authentication patternAuthentication implementation matches the pattern used in the dashboard route, ensuring consistency across API endpoints.
13-17: Proper RLS implementationQuery correctly includes
user_idfiltering to enforce Row Level Security.
19-19: Correct error handling for missing dataProperly handles the
PGRST116error code for cases where no nutrition goals exist yet.
40-77: Comprehensive validation logicThe validation covers all required fields with appropriate type and range checks. The structure is well-organized and provides clear error messages.
79-86: Good enum validationActivity level validation using an array of valid values is a clean approach that's easy to maintain.
97-111: Proper upsert operationThe upsert operation correctly includes
user_idandupdated_attimestamp, and uses.select().single()to return the updated record.src/components/ui/toast.tsx (6)
1-5: Proper imports and dependenciesClean imports with appropriate React patterns and utility functions.
7-21: Well-structured styling systemThe use of
class-variance-authorityfor variant management is a modern approach that provides type safety and maintainability for component styling.
23-25: Good TypeScript interface designThe interface properly extends React's div props while adding the custom
onClosecallback and variant props.
30-53: Excellent accessibility implementationThe component includes proper accessibility features:
forwardReffor ref forwardingsr-onlyclass for screen reader text- Proper button semantics for the close action
- Keyboard navigation support
41-49: Conditional close button renderingThe conditional rendering of the close button based on the
onCloseprop is clean and follows React best practices.
59-82: Consistent component patterns
ToastTitleandToastDescriptionfollow the same patterns as the main Toast component with proper ref forwarding and class name merging.next.config.ts (6)
6-8: Temporary ESLint configuration with clear TODOThe temporary disabling of ESLint during builds includes a clear TODO with a target timeline, which is good practice for technical debt management.
11-15: Appropriate experimental configurationThe server actions configuration restricting origins to localhost is appropriate for development security.
18-22: Modern image optimization settingsThe image configuration includes modern formats (WebP, AVIF) and comprehensive device/image sizes for responsive loading, which aligns with PWA best practices.
29-46: Comprehensive security headersThe security headers implementation is excellent:
X-Frame-Options: DENYprevents clickjackingX-Content-Type-Options: nosniffprevents MIME type sniffingReferrer-Policycontrols referrer informationPermissions-Policymanages feature access appropriately for the app
48-60: Proper service worker cachingThe service worker headers correctly prevent caching with
no-cache, no-store, must-revalidateto ensure the latest version is always loaded.
61-73: Appropriate manifest cachingThe manifest headers use long-term caching with
immutableflag, which is appropriate since manifests rarely change.src/components/calorie-tracker/FoodLogManager.tsx (1)
62-84: Well-implemented filtering logicThe filtering implementation properly handles edge cases with optional chaining and has correct effect dependencies. The search functionality covers both food item names and notes effectively.
src/components/providers/ToastProvider.tsx (1)
96-115: Well-implemented responsive toast containerThe toast container implementation handles responsive design effectively with proper positioning for mobile and desktop views. The conditional flex direction ensures toasts stack appropriately on different screen sizes.
src/components/calorie-tracker/DailyCalorieSummary.tsx (1)
62-123: Clean and well-structured UI implementationThe component demonstrates good practices:
- Proper handling of progress bar overflow with Math.min
- Consistent decimal formatting with toFixed(1)
- Clear visual hierarchy with appropriate spacing
- Good use of color coding for different macros
src/components/calorie-tracker/RapidMealLogger.tsx (2)
149-191: Well-implemented photo handling with proper error handlingThe photo capture implementation properly handles file reading, base64 conversion, and both online/offline scenarios with appropriate user feedback.
Minor suggestion: Consider adding validation for the base64 split operation:
const signedUrl = await syncService?.queuePhotoUpload({ fileName: photo.name, - base64: reader.result?.toString().split(',')[1], + base64: reader.result?.toString().split(',')[1] || '', mimeType: photo.type, user_id: user.id });
328-401: Excellent UI implementation with smooth animationsThe floating action button implementation is well-crafted with:
- Smooth rotation animation for the main button
- Staggered animation for menu items
- Proper backdrop handling for dismissal
- Clean use of the cn utility for conditional classes
src/components/providers/PWAProvider.tsx (3)
1-22: LGTM! Well-structured PWA event interface.The BeforeInstallPromptEvent interface properly types the PWA installation event with all necessary properties.
88-115: Good implementation of PWA install flow.The deferred install prompt with a 60-second delay provides a good balance between user experience and app promotion. Event handling and cleanup are properly implemented.
121-133: Excellent cleanup pattern implementation.The use of cleanupRef to track all cleanup functions and execute them on unmount is a robust pattern that prevents memory leaks.
src/app/quick-photo/page.tsx (3)
1-18: Correct Supabase client usage for client components.Properly uses
createClientfrom@/utils/supabase/clientas required by the coding guidelines for client components.
19-42: Well-implemented authentication check with proper error handling.The authentication flow correctly handles all cases: successful auth, auth errors, and general failures. The loading state management ensures a good user experience.
115-126: Clean component integration.The OptimizedCamera component is properly integrated with appropriate callbacks and configuration.
src/lib/pwa/__tests__/offline-storage.test.ts (5)
1-76: Excellent IndexedDB mock implementation.The mock setup comprehensively covers all IndexedDB operations needed for testing the OfflineStorage class. The structure allows for proper simulation of async database operations.
77-120: Comprehensive initialization test coverage.The tests properly validate database initialization, error handling, and schema creation during upgrades.
122-233: Thorough testing of store operations with proper field validation.The tests ensure all required fields (id, timestamp, synced, retryCount) are properly added to stored entries and error cases are handled.
297-430: Excellent coverage of sync operations including retry limit enforcement.The tests properly validate the retry limit mechanism and ensure entries cannot exceed the maximum retry attempts. All sync-related operations are well tested.
431-656: Comprehensive test coverage including edge cases and environment handling.The tests thoroughly cover all remaining functionality including caching, favorites, clearing data, and proper singleton behavior in different environments.
src/lib/pwa/service-worker.ts (3)
1-12: Well-typed service worker interface with sync support.The extended interface properly types the optional sync API that may not be available in all browsers.
13-40: Robust service worker registration with automatic update detection.The implementation properly handles service worker lifecycle events and implements a reasonable update check interval.
62-91: Well-implemented notification permission handling.The code properly checks for browser support and handles all permission states correctly.
src/components/calorie-tracker/RecentMeals.tsx (2)
1-28: Well-structured interfaces with comprehensive typing.The Meal interface properly types all necessary properties for displaying meal information.
77-160: Well-implemented meal list with good UX patterns.The component handles all states properly (processing, completed, failed) and provides clear visual feedback. The image handling with proper alt text and the meal item truncation with "+N more" pattern are well implemented.
src/lib/pwa/__tests__/sync-service.test.ts (4)
95-331: LGTM! Comprehensive test coverage for manual sync scenarios.The manual sync tests thoroughly cover:
- Empty queue handling
- Different entry types (meal logs, photo uploads)
- API error handling with retry logic
- Network error scenarios
- Partial sync success/failure
- Maximum retry attempts and deletion
The mock data structures are well-defined and the assertions properly verify the expected behavior.
392-447: LGTM! Well-structured background sync registration tests.The tests properly:
- Mock the service worker registration and sync API
- Cover all scenarios (sync supported, not supported, no service worker)
- Clean up console spies after use
- Use appropriate assertions for each case
Good use of
Object.definePropertyfor mocking browser APIs.
449-462: LGTM! Correctly tests server-side rendering scenario.The test properly verifies that
syncServiceis null in a server environment wherewindowis undefined. Good use ofjest.isolateModulesto ensure the module is re-evaluated with the mocked environment.
464-508: LGTM! Thorough error recovery testing.The tests effectively verify:
- Sync continues processing remaining entries even when some fail
- Storage errors are gracefully handled with appropriate error messages
- Mixed success/failure scenarios return accurate counts
Good coverage of error resilience in the sync process.
docs/phase-3-technical-implementation.md (2)
1-8: LGTM! Documentation accurately reflects Phase 3 completion status.The updates appropriately:
- Mark Phase 3 as completed with clear visual indicators
- Update the overview to past tense reflecting actual implementation
- Add celebration emoji for better visual feedback
Good practice to update documentation to reflect current project status.
1253-1276: Well-documented implementation timeline with clear status indicators.Good transparency in documenting:
- Completed items with checkmarks
- Explicitly noting deferred items with reasons
- Partial completion status for Week 4
This provides excellent traceability for project progress.
src/app/calorie-tracker/page.tsx (1)
29-32: LGTM! Proper authentication and RLS implementation.The code correctly:
- Performs server-side authentication check
- Includes
user_idin all health data queries as required by the coding guidelines- Uses the appropriate Supabase client for server components
This ensures Row Level Security (RLS) is properly enforced.
Also applies to: 65-66, 77-78, 88-89
src/components/calorie-tracker/AIAnalysisDisplay.tsx (1)
17-63: LGTM! Well-structured component with good accessibility practices.Excellent implementation:
- Clear prop interface with optional callbacks
- Helper functions with sensible thresholds
- Proper aria-labels on status icons for screen readers
- Clean separation of display logic
src/lib/image/__tests__/optimizer.test.ts (2)
1-45: LGTM! Comprehensive mock setup for browser APIs.Excellent mock implementation:
- Proper async simulation for image loading
- Complete canvas API mocking
- All necessary methods mocked for testing
The use of
setTimeout(..., 0)for async image loading simulation is a good testing pattern.
465-486: Excellent memory management test coverage!Great attention to resource cleanup:
- Verifies object URLs are revoked on success
- Ensures cleanup happens even on errors
- Prevents potential memory leaks
This is often overlooked in browser-based image processing but is crucial for long-running applications.
docs/pwa-features.md (1)
1-133: Documentation looks comprehensive and well-structured!The PWA features documentation provides clear installation instructions, usage guidelines, troubleshooting tips, and browser compatibility information. The content is well-organized and should help users understand and utilize the PWA capabilities effectively.
supabase/migrations/20250715221416_phase3_daily_summaries.sql (1)
1-135: Database schema implementation looks solid with proper RLS!The migration correctly implements:
- Row Level Security policies using
auth.uid()for both tables as required- Appropriate indexes on user_id and date columns for query performance
- Trigger function that properly handles INSERT/UPDATE/DELETE operations
- Timestamp update triggers for audit trails
The schema design follows best practices for health data security.
src/components/calorie-tracker/OptimizedCamera.tsx (1)
234-253: Excellent error handling implementation!The camera error display provides clear, actionable feedback to users with specific error messages for different failure scenarios and retry/cancel options.
public/sw.js (1)
66-160: Well-implemented caching strategies!The fetch event handler correctly implements:
- Network-first strategy for API calls with cache fallback
- Cache-first strategy for static assets
- Proper cache expiration using custom headers
- Offline fallback for document requests
The implementation follows PWA best practices.
src/components/calorie-tracker/FavoriteFoods.tsx (1)
54-69: Proper authentication implementation!The component correctly:
- Imports Supabase client from the appropriate path for client components
- Checks authentication on mount
- Handles auth errors gracefully
src/lib/image/optimizer.ts (1)
16-89: Well-implemented image optimization with proper memory management.The method correctly handles Canvas context availability, implements proper error handling, and ensures memory cleanup with
URL.revokeObjectURLin both success and error paths.src/lib/pwa/offline-storage.ts (3)
95-136: Good IndexedDB initialization with proper error handlingThe initialization method correctly sets up the database with appropriate object stores and indexes. The upgrade handling is well-structured.
207-237: Well-implemented retry logic with proper bounds checkingThe
incrementRetryCountmethod correctly enforces the maximum retry limit and provides clear error messages when the limit is exceeded.
353-365: Well-implemented singleton pattern with proper environment checksThe singleton implementation correctly handles server-side rendering scenarios by checking for the browser environment. The factory function provides clear error messages for both SSR and initialization failures.
| // Try to use the global toast function if available | ||
| if (globalToastFn) { | ||
| globalToastFn({ title, description, variant, duration }); | ||
| } else { | ||
| // Fallback - log a warning that toast system isn't ready | ||
| console.warn('Toast system not initialized, message:', message); | ||
| } | ||
| }, []); |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Improve error handling for uninitialized toast system.
The current approach warns about uninitialized toast but doesn't provide a graceful fallback. Consider storing messages to replay when the system is ready.
+// Queue for messages when toast system isn't ready
+const messageQueue: ToastOptions[] = [];
+
+function flushMessageQueue() {
+ if (globalToastFn) {
+ while (messageQueue.length > 0) {
+ const message = messageQueue.shift()!;
+ globalToastFn(message);
+ }
+ }
+}
export function setGlobalToast(toastFn: (options: ToastOptions) => void) {
globalToastFn = toastFn;
+ flushMessageQueue();
}
// Try to use the global toast function if available
if (globalToastFn) {
globalToastFn({ title, description, variant, duration });
} else {
- // Fallback - log a warning that toast system isn't ready
- console.warn('Toast system not initialized, message:', message);
+ // Queue the message for when the system is ready
+ messageQueue.push({ title, description, variant, duration });
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // Try to use the global toast function if available | |
| if (globalToastFn) { | |
| globalToastFn({ title, description, variant, duration }); | |
| } else { | |
| // Fallback - log a warning that toast system isn't ready | |
| console.warn('Toast system not initialized, message:', message); | |
| } | |
| }, []); | |
| // Queue for messages when toast system isn't ready | |
| const messageQueue: ToastOptions[] = []; | |
| function flushMessageQueue() { | |
| if (globalToastFn) { | |
| while (messageQueue.length > 0) { | |
| const message = messageQueue.shift()!; | |
| globalToastFn(message); | |
| } | |
| } | |
| } | |
| export function setGlobalToast(toastFn: (options: ToastOptions) => void) { | |
| globalToastFn = toastFn; | |
| flushMessageQueue(); | |
| } | |
| // … other hook code … | |
| // Try to use the global toast function if available | |
| if (globalToastFn) { | |
| globalToastFn({ title, description, variant, duration }); | |
| } else { | |
| // Queue the message for when the system is ready | |
| messageQueue.push({ title, description, variant, duration }); | |
| } | |
| }, []); |
🤖 Prompt for AI Agents
In src/hooks/use-toast.ts around lines 29 to 36, the code currently logs a
warning if the toast system is not initialized but does not handle the message
gracefully. Modify the code to store the toast messages in a queue or array when
the global toast function is unavailable, so these messages can be replayed or
displayed once the toast system becomes ready, ensuring no messages are lost.
| fat: { color: '#f59e0b', label: 'Fat (g)' }, | ||
| }; | ||
|
|
||
| const averageCalories = weekData.reduce((sum, day) => sum + day.calories, 0) / weekData.length; |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Handle division by zero edge case.
The average calculation could result in NaN if weekData is empty.
- const averageCalories = weekData.reduce((sum, day) => sum + day.calories, 0) / weekData.length;
+ const averageCalories = weekData.length > 0
+ ? weekData.reduce((sum, day) => sum + day.calories, 0) / weekData.length
+ : 0;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const averageCalories = weekData.reduce((sum, day) => sum + day.calories, 0) / weekData.length; | |
| const averageCalories = weekData.length > 0 | |
| ? weekData.reduce((sum, day) => sum + day.calories, 0) / weekData.length | |
| : 0; |
🤖 Prompt for AI Agents
In src/components/calorie-tracker/WeeklyTrendsChart.tsx at line 80, the
calculation of averageCalories divides by weekData.length without checking if
weekData is empty, which can cause a division by zero and result in NaN. Fix
this by adding a condition to check if weekData.length is greater than zero
before dividing; if it's zero, set averageCalories to zero or an appropriate
default value.
| {Object.entries(metricConfig).map(([key, config]) => ( | ||
| <button | ||
| key={key} | ||
| onClick={() => setActiveMetric(key as any)} |
There was a problem hiding this comment.
Fix TypeScript type assertion.
Using as any defeats the purpose of TypeScript. Use proper type assertion instead.
- onClick={() => setActiveMetric(key as any)}
+ onClick={() => setActiveMetric(key as keyof typeof metricConfig)}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| onClick={() => setActiveMetric(key as any)} | |
| onClick={() => setActiveMetric(key as keyof typeof metricConfig)} |
🤖 Prompt for AI Agents
In src/components/calorie-tracker/WeeklyTrendsChart.tsx at line 97, replace the
use of 'as any' in the onClick handler with a proper TypeScript type assertion
that matches the expected type of the setActiveMetric function parameter.
Identify the correct type for 'key' and assert it accordingly to maintain type
safety.
| useEffect(() => { | ||
| fetchWeeklyData(); | ||
| }, [userId]); |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Add dependency to useEffect for fetchWeeklyData.
The fetchWeeklyData function should be included in the dependency array or wrapped in useCallback to prevent unnecessary re-renders.
+ const fetchWeeklyData = useCallback(async () => {
// ... existing function body ...
- };
+ }, [userId]);
useEffect(() => {
fetchWeeklyData();
- }, [userId]);
+ }, [fetchWeeklyData]);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| useEffect(() => { | |
| fetchWeeklyData(); | |
| }, [userId]); | |
| const fetchWeeklyData = useCallback(async () => { | |
| // ... existing function body ... | |
| }, [userId]); | |
| useEffect(() => { | |
| fetchWeeklyData(); | |
| }, [fetchWeeklyData]); |
🤖 Prompt for AI Agents
In src/components/calorie-tracker/WeeklyTrendsChart.tsx around lines 28 to 30,
the useEffect hook depends on the fetchWeeklyData function but it is not
included in the dependency array. To fix this, either add fetchWeeklyData to the
dependency array or wrap fetchWeeklyData in a useCallback hook to memoize it,
ensuring that useEffect only re-runs when necessary and preventing unnecessary
re-renders.
| const fetchWeeklyData = async () => { | ||
| try { | ||
| const supabase = createClient(); | ||
| const endDate = new Date(); | ||
| const startDate = subDays(endDate, 6); | ||
|
|
||
| const { data, error } = await supabase | ||
| .from('daily_nutrition_summaries') | ||
| .select('*') | ||
| .eq('user_id', userId) | ||
| .gte('date', format(startDate, 'yyyy-MM-dd')) | ||
| .lte('date', format(endDate, 'yyyy-MM-dd')) | ||
| .order('date', { ascending: true }); | ||
|
|
||
| if (error) throw error; | ||
|
|
||
| // Fill in missing days with zero values | ||
| const weekData: DayData[] = []; | ||
| for (let i = 0; i < 7; i++) { | ||
| const date = subDays(endDate, 6 - i); | ||
| const dateStr = format(date, 'yyyy-MM-dd'); | ||
| const dayData = data?.find(d => d.date === dateStr); | ||
|
|
||
| weekData.push({ | ||
| date: dateStr, | ||
| calories: dayData?.total_calories || 0, | ||
| protein: dayData?.total_protein_g || 0, | ||
| carbs: dayData?.total_carbs_g || 0, | ||
| fat: dayData?.total_fat_g || 0, | ||
| dayLabel: format(date, 'EEE'), | ||
| }); | ||
| } | ||
|
|
||
| setWeekData(weekData); | ||
| } catch (error) { | ||
| console.error('Error fetching weekly data:', error); | ||
| } finally { | ||
| setLoading(false); | ||
| } | ||
| }; |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Add proper error handling and user feedback.
The current error handling only logs to console. Users should be informed when data fetching fails, especially since this affects the chart display.
+import { useToast } from '@/hooks/use-toast';
export function WeeklyTrendsChart({ userId }: WeeklyTrendsProps) {
const [weekData, setWeekData] = useState<DayData[]>([]);
const [loading, setLoading] = useState(true);
+ const [error, setError] = useState<string | null>(null);
const [activeMetric, setActiveMetric] = useState<'calories' | 'protein' | 'carbs' | 'fat'>('calories');
+ const { toast } = useToast();
const fetchWeeklyData = async () => {
try {
+ setError(null);
const supabase = createClient();
// ... existing code ...
setWeekData(weekData);
} catch (error) {
console.error('Error fetching weekly data:', error);
+ const errorMessage = 'Failed to load weekly trends data';
+ setError(errorMessage);
+ toast({
+ title: 'Error',
+ description: errorMessage,
+ variant: 'destructive'
+ });
} finally {
setLoading(false);
}
};🤖 Prompt for AI Agents
In src/components/calorie-tracker/WeeklyTrendsChart.tsx around lines 32 to 71,
the error handling in fetchWeeklyData only logs errors to the console without
informing users. Enhance the error handling by adding a state variable to track
error messages and update it when an error occurs. Display a user-friendly error
message in the UI so users are aware when data fetching fails and the chart
cannot be displayed properly.
| case 'photo_upload': | ||
| const signedUrl = await this.syncPhotoUpload(supabase, entry.data as PhotoUploadData); | ||
| // Note: For background sync, we don't return the URL as the original caller is no longer waiting | ||
| console.log('Photo uploaded with signed URL:', signedUrl); | ||
| break; |
There was a problem hiding this comment.
Scope the variable declaration to prevent access from other switch cases.
Variables declared in switch cases without blocks can be accessed from other cases, potentially causing bugs.
case 'photo_upload':
+ {
const signedUrl = await this.syncPhotoUpload(supabase, entry.data as PhotoUploadData);
// Note: For background sync, we don't return the URL as the original caller is no longer waiting
console.log('Photo uploaded with signed URL:', signedUrl);
break;
+ }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| case 'photo_upload': | |
| const signedUrl = await this.syncPhotoUpload(supabase, entry.data as PhotoUploadData); | |
| // Note: For background sync, we don't return the URL as the original caller is no longer waiting | |
| console.log('Photo uploaded with signed URL:', signedUrl); | |
| break; | |
| case 'photo_upload': | |
| { | |
| const signedUrl = await this.syncPhotoUpload(supabase, entry.data as PhotoUploadData); | |
| // Note: For background sync, we don't return the URL as the original caller is no longer waiting | |
| console.log('Photo uploaded with signed URL:', signedUrl); | |
| break; | |
| } |
🧰 Tools
🪛 Biome (1.9.4)
[error] 150-150: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.
The declaration is defined in this switch clause:
Unsafe fix: Wrap the declaration in a block.
(lint/correctness/noSwitchDeclarations)
🤖 Prompt for AI Agents
In src/lib/pwa/sync-service.ts around lines 149 to 153, the variable signedUrl
is declared directly inside a switch case without a block, which allows it to be
accessed from other cases and may cause bugs. To fix this, wrap the code inside
the 'photo_upload' case in curly braces to create a block scope, ensuring
signedUrl is scoped only to this case.
| async store(entry: Omit<OfflineEntry, 'id' | 'timestamp' | 'synced' | 'retryCount'>): Promise<string> { | ||
| if (!this.db) { | ||
| await this.init(); | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Extract repeated database initialization check
The pattern of checking if (!this.db) await this.init() is repeated in every method. Consider extracting this to a private method to reduce duplication.
+ private async ensureInitialized(): Promise<void> {
+ if (!this.db) {
+ await this.init();
+ }
+ }
async store(entry: Omit<OfflineEntry, 'id' | 'timestamp' | 'synced' | 'retryCount'>): Promise<string> {
- if (!this.db) {
- await this.init();
- }
+ await this.ensureInitialized();Apply this pattern to all methods that check for database initialization.
Also applies to: 162-165, 181-184, 207-210, 239-242, 254-257, 269-272, 291-294, 306-309, 332-335
🤖 Prompt for AI Agents
In src/lib/pwa/offline-storage.ts around lines 138 to 141 and similarly at lines
162-165, 181-184, 207-210, 239-242, 254-257, 269-272, 291-294, 306-309, and
332-335, the code repeatedly checks if the database is initialized with `if
(!this.db) await this.init()`. Extract this repeated check into a private
method, for example, `private async ensureDbInitialized()`, that performs this
check and initialization. Replace all occurrences of the repeated check in these
methods with a call to this new private method to reduce duplication and improve
code maintainability.
| interface PhotoUploadData { | ||
| fileName: string; | ||
| base64?: string; | ||
| mimeType: string; | ||
| user_id: string; | ||
| } |
There was a problem hiding this comment.
Make base64 field required for offline photo uploads
The base64 field is optional in PhotoUploadData, but for offline storage of photos, this field would be essential. Without it, the photo data cannot be stored offline.
interface PhotoUploadData {
fileName: string;
- base64?: string;
+ base64: string;
mimeType: string;
user_id: string;
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| interface PhotoUploadData { | |
| fileName: string; | |
| base64?: string; | |
| mimeType: string; | |
| user_id: string; | |
| } | |
| interface PhotoUploadData { | |
| fileName: string; | |
| base64: string; | |
| mimeType: string; | |
| user_id: string; | |
| } |
🤖 Prompt for AI Agents
In src/lib/pwa/offline-storage.ts around lines 27 to 32, the base64 field in the
PhotoUploadData interface is optional but should be required for offline photo
uploads to ensure photo data is available for storage. Change the base64 field
from optional to required by removing the question mark so that all
PhotoUploadData objects must include base64 data.
| // Specific data interfaces for each entry type | ||
| interface MealLogData { | ||
| food_name?: string; | ||
| meal_name?: string; | ||
| food_items?: Array<{ | ||
| name: string; | ||
| calories: number; | ||
| protein_g: number; | ||
| carbs_g: number; | ||
| fat_g: number; | ||
| fiber_g: number; | ||
| quantity: number; | ||
| unit: string; | ||
| }>; | ||
| total_calories?: number; | ||
| total_protein_g?: number; | ||
| total_carbs_g?: number; | ||
| total_fat_g?: number; | ||
| total_fiber_g?: number; | ||
| confidence_score: number; | ||
| notes?: string; | ||
| meal_date: string; | ||
| meal_type: string; | ||
| user_id: string; | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Consider making critical fields required in MealLogData
The MealLogData interface has many optional fields that seem essential for a meal log. Fields like food_name or meal_name (at least one should be required), and nutrition totals should probably be required to ensure data consistency.
Additionally, consider if food_items and individual food fields (food_name) are mutually exclusive patterns that should be enforced through union types:
-interface MealLogData {
- food_name?: string;
- meal_name?: string;
- food_items?: Array<{
+type MealLogData = {
+ meal_date: string;
+ meal_type: string;
+ user_id: string;
+ confidence_score: number;
+ notes?: string;
+} & (
+ | {
+ meal_name: string;
+ food_items: Array<{
+ name: string;
+ calories: number;
+ protein_g: number;
+ carbs_g: number;
+ fat_g: number;
+ fiber_g: number;
+ quantity: number;
+ unit: string;
+ }>;
+ total_calories: number;
+ total_protein_g: number;
+ total_carbs_g: number;
+ total_fat_g: number;
+ total_fiber_g: number;
+ }
+ | {
+ food_name: string;
+ total_calories: number;
+ total_protein_g: number;
+ total_carbs_g: number;
+ total_fat_g: number;
+ total_fiber_g: number;
+ }
+);Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In src/lib/pwa/offline-storage.ts lines 1 to 25, the MealLogData interface has
many optional fields that are critical for data consistency. Make fields like
food_name or meal_name required by enforcing that at least one is present, and
also make total_calories, total_protein_g, total_carbs_g, total_fat_g, and
total_fiber_g required. Additionally, refactor the interface to use a union type
to enforce that either food_items or food_name is present, but not both, to
clearly represent mutually exclusive data patterns.
| // Specific data interfaces for each entry type | ||
| interface MealLogData { | ||
| food_name?: string; | ||
| meal_name?: string; | ||
| food_items?: Array<{ | ||
| name: string; | ||
| calories: number; | ||
| protein_g: number; | ||
| carbs_g: number; | ||
| fat_g: number; | ||
| fiber_g: number; | ||
| quantity: number; | ||
| unit: string; | ||
| }>; | ||
| total_calories?: number; | ||
| total_protein_g?: number; | ||
| total_carbs_g?: number; | ||
| total_fat_g?: number; | ||
| total_fiber_g?: number; | ||
| confidence_score: number; | ||
| notes?: string; | ||
| meal_date: string; | ||
| meal_type: string; | ||
| user_id: string; | ||
| } | ||
|
|
||
| interface PhotoUploadData { | ||
| fileName: string; | ||
| base64?: string; | ||
| mimeType: string; | ||
| user_id: string; | ||
| } | ||
|
|
||
| interface UserActionData { | ||
| action: string; | ||
| payload: Record<string, unknown>; | ||
| user_id: string; | ||
| } | ||
|
|
||
| // Union type for all possible data types | ||
| type OfflineEntryData = MealLogData | PhotoUploadData | UserActionData; | ||
|
|
||
| // Interfaces for cached meals and favorite foods | ||
| interface CachedMeal { | ||
| id: string; | ||
| food_items: Array<{ | ||
| name: string; | ||
| calories: number; | ||
| protein_g: number; | ||
| carbs_g: number; | ||
| fat_g: number; | ||
| fiber_g: number; | ||
| quantity: number; | ||
| unit: string; | ||
| }>; | ||
| total_calories: number; | ||
| total_protein_g: number; | ||
| total_carbs_g: number; | ||
| total_fat_g: number; | ||
| total_fiber_g: number; | ||
| confidence_score: number; | ||
| notes?: string; | ||
| meal_date: string; | ||
| meal_type: string; | ||
| user_id: string; | ||
| created_at: string; | ||
| image_url?: string; | ||
| } | ||
|
|
||
| interface FavoriteFood { | ||
| id: string; | ||
| name: string; | ||
| calories: number; | ||
| macros: { | ||
| protein: number; | ||
| carbs: number; | ||
| fat: number; | ||
| fiber: number; | ||
| }; | ||
| imageUrl?: string; | ||
| frequency: number; | ||
| lastUsed: Date; | ||
| tags?: string[]; | ||
| } | ||
|
|
||
| interface OfflineEntry { | ||
| id: string; | ||
| type: 'meal_log' | 'photo_upload' | 'user_action'; | ||
| data: OfflineEntryData; | ||
| timestamp: number; | ||
| synced: boolean; | ||
| retryCount: number; | ||
| } | ||
|
|
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
Missing PII encryption as required by coding guidelines
According to the coding guidelines, personally identifiable health data (PII) should be encrypted before storing in the database. The current implementation stores user meal data, photos, and other potentially sensitive information without encryption.
Consider implementing encryption for sensitive fields before storing in IndexedDB. Fields that may contain PII include:
food_name,meal_name,notesin MealLogDatabase64image data in PhotoUploadData- User action payloads
You'll need to implement encryption/decryption utilities and apply them in the store method and when retrieving data.
Let me verify if encryption utilities already exist in the codebase:
Also applies to: 138-160
🏁 Script executed:
#!/bin/bash
# Search for existing encryption utilities
fd -e ts -e js | xargs rg -l "encrypt|crypto|cipher" -A 5Length of output: 59
🏁 Script executed:
#!/bin/bash
# Check for encryption libraries in package.json
echo "Checking package.json for encryption-related dependencies..."
grep -R "\"crypto-js\"" package.json || echo " – crypto-js not found"
grep -R "\"@aws-crypto\"" package.json || echo " – @aws-crypto not found"
grep -R "\"crypto\"" package.json || echo " – generic crypto not found"
# Search code for require/import of 'crypto'
echo "Searching for require('crypto') usage..."
rg "require\(['\"]crypto['\"]\)" -n || echo " – no require('crypto') imports found"
echo "Searching for import statements involving crypto..."
rg "import .*crypto" -n || echo " – no import crypto statements found"Length of output: 881
Implement PII encryption in offline storage
No existing encryption utilities were found in the codebase or dependencies. As per our security guidelines, all personally identifiable health data must be encrypted before persisting to IndexedDB.
Please update src/lib/pwa/offline-storage.ts to:
• Integrate an encryption/decryption utility (e.g. Web Crypto API or a vetted library)
• Encrypt sensitive fields in your store method
• Decrypt them in your retrieve method
Affected data interfaces and fields:
- MealLogData:
food_name,meal_name,notes - PhotoUploadData:
base64 - UserActionData: sensitive values in
payload
You’ll also need to manage encryption keys securely (e.g. derive from user credentials or use a secure keystore) and update any import paths once you’ve added the utility.
🤖 Prompt for AI Agents
In src/lib/pwa/offline-storage.ts from lines 1 to 94, implement encryption and
decryption for PII fields before storing and after retrieving data from
IndexedDB. Use a secure encryption utility such as the Web Crypto API or a
vetted library, and manage encryption keys securely, for example by deriving
them from user credentials or using a secure keystore. Encrypt the fields
food_name, meal_name, and notes in MealLogData; the base64 field in
PhotoUploadData; and sensitive values inside the payload of UserActionData.
Update the store method to encrypt these fields before saving and the retrieve
method to decrypt them after loading. Also, add necessary imports and key
management logic accordingly.
🛠️ Refactor suggestion
Export TypeScript interfaces for better type safety
The interfaces defined in this file (MealLogData, PhotoUploadData, CachedMeal, FavoriteFood, etc.) should be exported so that consumers of this module can use them for type safety.
-interface MealLogData {
+export interface MealLogData {
-interface PhotoUploadData {
+export interface PhotoUploadData {
-interface UserActionData {
+export interface UserActionData {
-type OfflineEntryData = MealLogData | PhotoUploadData | UserActionData;
+export type OfflineEntryData = MealLogData | PhotoUploadData | UserActionData;
-interface CachedMeal {
+export interface CachedMeal {
-interface FavoriteFood {
+export interface FavoriteFood {
-interface OfflineEntry {
+export interface OfflineEntry {📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // Specific data interfaces for each entry type | |
| interface MealLogData { | |
| food_name?: string; | |
| meal_name?: string; | |
| food_items?: Array<{ | |
| name: string; | |
| calories: number; | |
| protein_g: number; | |
| carbs_g: number; | |
| fat_g: number; | |
| fiber_g: number; | |
| quantity: number; | |
| unit: string; | |
| }>; | |
| total_calories?: number; | |
| total_protein_g?: number; | |
| total_carbs_g?: number; | |
| total_fat_g?: number; | |
| total_fiber_g?: number; | |
| confidence_score: number; | |
| notes?: string; | |
| meal_date: string; | |
| meal_type: string; | |
| user_id: string; | |
| } | |
| interface PhotoUploadData { | |
| fileName: string; | |
| base64?: string; | |
| mimeType: string; | |
| user_id: string; | |
| } | |
| interface UserActionData { | |
| action: string; | |
| payload: Record<string, unknown>; | |
| user_id: string; | |
| } | |
| // Union type for all possible data types | |
| type OfflineEntryData = MealLogData | PhotoUploadData | UserActionData; | |
| // Interfaces for cached meals and favorite foods | |
| interface CachedMeal { | |
| id: string; | |
| food_items: Array<{ | |
| name: string; | |
| calories: number; | |
| protein_g: number; | |
| carbs_g: number; | |
| fat_g: number; | |
| fiber_g: number; | |
| quantity: number; | |
| unit: string; | |
| }>; | |
| total_calories: number; | |
| total_protein_g: number; | |
| total_carbs_g: number; | |
| total_fat_g: number; | |
| total_fiber_g: number; | |
| confidence_score: number; | |
| notes?: string; | |
| meal_date: string; | |
| meal_type: string; | |
| user_id: string; | |
| created_at: string; | |
| image_url?: string; | |
| } | |
| interface FavoriteFood { | |
| id: string; | |
| name: string; | |
| calories: number; | |
| macros: { | |
| protein: number; | |
| carbs: number; | |
| fat: number; | |
| fiber: number; | |
| }; | |
| imageUrl?: string; | |
| frequency: number; | |
| lastUsed: Date; | |
| tags?: string[]; | |
| } | |
| interface OfflineEntry { | |
| id: string; | |
| type: 'meal_log' | 'photo_upload' | 'user_action'; | |
| data: OfflineEntryData; | |
| timestamp: number; | |
| synced: boolean; | |
| retryCount: number; | |
| } | |
| // Specific data interfaces for each entry type | |
| export interface MealLogData { | |
| food_name?: string; | |
| meal_name?: string; | |
| food_items?: Array<{ | |
| name: string; | |
| calories: number; | |
| protein_g: number; | |
| carbs_g: number; | |
| fat_g: number; | |
| fiber_g: number; | |
| quantity: number; | |
| unit: string; | |
| }>; | |
| total_calories?: number; | |
| total_protein_g?: number; | |
| total_carbs_g?: number; | |
| total_fat_g?: number; | |
| total_fiber_g?: number; | |
| confidence_score: number; | |
| notes?: string; | |
| meal_date: string; | |
| meal_type: string; | |
| user_id: string; | |
| } | |
| export interface PhotoUploadData { | |
| fileName: string; | |
| base64?: string; | |
| mimeType: string; | |
| user_id: string; | |
| } | |
| export interface UserActionData { | |
| action: string; | |
| payload: Record<string, unknown>; | |
| user_id: string; | |
| } | |
| // Union type for all possible data types | |
| export type OfflineEntryData = MealLogData | PhotoUploadData | UserActionData; | |
| // Interfaces for cached meals and favorite foods | |
| export interface CachedMeal { | |
| id: string; | |
| food_items: Array<{ | |
| name: string; | |
| calories: number; | |
| protein_g: number; | |
| carbs_g: number; | |
| fat_g: number; | |
| fiber_g: number; | |
| quantity: number; | |
| unit: string; | |
| }>; | |
| total_calories: number; | |
| total_protein_g: number; | |
| total_carbs_g: number; | |
| total_fat_g: number; | |
| total_fiber_g: number; | |
| confidence_score: number; | |
| notes?: string; | |
| meal_date: string; | |
| meal_type: string; | |
| user_id: string; | |
| created_at: string; | |
| image_url?: string; | |
| } | |
| export interface FavoriteFood { | |
| id: string; | |
| name: string; | |
| calories: number; | |
| macros: { | |
| protein: number; | |
| carbs: number; | |
| fat: number; | |
| fiber: number; | |
| }; | |
| imageUrl?: string; | |
| frequency: number; | |
| lastUsed: Date; | |
| tags?: string[]; | |
| } | |
| export interface OfflineEntry { | |
| id: string; | |
| type: 'meal_log' | 'photo_upload' | 'user_action'; | |
| data: OfflineEntryData; | |
| timestamp: number; | |
| synced: boolean; | |
| retryCount: number; | |
| } |
🤖 Prompt for AI Agents
In src/lib/pwa/offline-storage.ts from lines 1 to 94, the TypeScript interfaces
such as MealLogData, PhotoUploadData, CachedMeal, FavoriteFood, and others are
currently not exported, limiting their usability outside this module. To fix
this, add the export keyword before each interface and type declaration so they
can be imported and used for type safety in other parts of the application.
| interface CachedMeal { | ||
| id: string; | ||
| food_items: Array<{ | ||
| name: string; | ||
| calories: number; | ||
| protein_g: number; | ||
| carbs_g: number; | ||
| fat_g: number; | ||
| fiber_g: number; | ||
| quantity: number; | ||
| unit: string; | ||
| }>; | ||
| total_calories: number; | ||
| total_protein_g: number; | ||
| total_carbs_g: number; | ||
| total_fat_g: number; | ||
| total_fiber_g: number; | ||
| confidence_score: number; | ||
| notes?: string; | ||
| meal_date: string; | ||
| meal_type: string; | ||
| user_id: string; | ||
| created_at: string; | ||
| image_url?: string; | ||
| } | ||
|
|
||
| interface FavoriteFood { | ||
| id: string; | ||
| name: string; | ||
| calories: number; | ||
| macros: { | ||
| protein: number; | ||
| carbs: number; | ||
| fat: number; | ||
| fiber: number; | ||
| }; | ||
| imageUrl?: string; | ||
| frequency: number; | ||
| lastUsed: Date; | ||
| tags?: string[]; | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Reduce interface duplication and fix naming inconsistencies
CachedMealduplicates most fields fromMealLogData. Consider extending or composing from a base interface.FavoriteFooduses different naming for macros (e.g.,proteinvsprotein_gused elsewhere).
+// Base nutrition interface
+interface NutritionData {
+ total_calories: number;
+ total_protein_g: number;
+ total_carbs_g: number;
+ total_fat_g: number;
+ total_fiber_g: number;
+}
+// Reuse the base interface
+interface CachedMeal extends NutritionData {
id: string;
food_items: Array<{
name: string;
calories: number;
protein_g: number;
carbs_g: number;
fat_g: number;
fiber_g: number;
quantity: number;
unit: string;
}>;
- total_calories: number;
- total_protein_g: number;
- total_carbs_g: number;
- total_fat_g: number;
- total_fiber_g: number;
confidence_score: number;
notes?: string;
meal_date: string;
meal_type: string;
user_id: string;
created_at: string;
image_url?: string;
}
interface FavoriteFood {
id: string;
name: string;
calories: number;
macros: {
- protein: number;
- carbs: number;
- fat: number;
- fiber: number;
+ protein_g: number;
+ carbs_g: number;
+ fat_g: number;
+ fiber_g: number;
};
imageUrl?: string;
frequency: number;
lastUsed: Date;
tags?: string[];
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| interface CachedMeal { | |
| id: string; | |
| food_items: Array<{ | |
| name: string; | |
| calories: number; | |
| protein_g: number; | |
| carbs_g: number; | |
| fat_g: number; | |
| fiber_g: number; | |
| quantity: number; | |
| unit: string; | |
| }>; | |
| total_calories: number; | |
| total_protein_g: number; | |
| total_carbs_g: number; | |
| total_fat_g: number; | |
| total_fiber_g: number; | |
| confidence_score: number; | |
| notes?: string; | |
| meal_date: string; | |
| meal_type: string; | |
| user_id: string; | |
| created_at: string; | |
| image_url?: string; | |
| } | |
| interface FavoriteFood { | |
| id: string; | |
| name: string; | |
| calories: number; | |
| macros: { | |
| protein: number; | |
| carbs: number; | |
| fat: number; | |
| fiber: number; | |
| }; | |
| imageUrl?: string; | |
| frequency: number; | |
| lastUsed: Date; | |
| tags?: string[]; | |
| } | |
| // Base nutrition interface | |
| interface NutritionData { | |
| total_calories: number; | |
| total_protein_g: number; | |
| total_carbs_g: number; | |
| total_fat_g: number; | |
| total_fiber_g: number; | |
| } | |
| interface CachedMeal extends NutritionData { | |
| id: string; | |
| food_items: Array<{ | |
| name: string; | |
| calories: number; | |
| protein_g: number; | |
| carbs_g: number; | |
| fat_g: number; | |
| fiber_g: number; | |
| quantity: number; | |
| unit: string; | |
| }>; | |
| confidence_score: number; | |
| notes?: string; | |
| meal_date: string; | |
| meal_type: string; | |
| user_id: string; | |
| created_at: string; | |
| image_url?: string; | |
| } | |
| interface FavoriteFood { | |
| id: string; | |
| name: string; | |
| calories: number; | |
| macros: { | |
| protein_g: number; | |
| carbs_g: number; | |
| fat_g: number; | |
| fiber_g: number; | |
| }; | |
| imageUrl?: string; | |
| frequency: number; | |
| lastUsed: Date; | |
| tags?: string[]; | |
| } |
🤖 Prompt for AI Agents
In src/lib/pwa/offline-storage.ts between lines 44 and 84, the CachedMeal
interface duplicates many fields from MealLogData, and FavoriteFood has
inconsistent macro naming compared to other parts of the code. Refactor
CachedMeal to extend or compose from a shared base interface to avoid
duplication. Rename the macros in FavoriteFood to use consistent naming with the
rest of the codebase, such as protein_g, carbs_g, fat_g, and fiber_g, to
maintain uniformity.
Implements: #31
Implements: #32
Summary by CodeRabbit
New Features
User Interface Enhancements
API & Data Management
Documentation
Bug Fixes
Chores & Testing
Style & Refactor